/**
 * 1. 了解 Record: type res = record<'a' | 'b', 1>;
 * Record<string, any> ===> key 为 string 类型，value 为任意类型的索引类型
 * 返回类型: type Res = Record<string, any>
 * 
 * 2.ParseQueryString 通过模式匹配提取，按照&分割开，推断最终生成的属性（a、b、c)
 *  infer声明的是局部变量
 */

// 按xxx&xxx模式切割
type ParseQueryString<QueryString extends string> =
    QueryString extends `${infer Param}&${infer Rest}` ? MergeParams<ParseParam<Param>, ParseQueryString<Rest>> : ParseParam<QueryString>;

/**
 测试ParseQueryString
 type ParseQueryStringResult = ParseQueryString<'a=1&a=2&b=3&c=4'>

 type ParseQueryStringResult = {
    readonly a: ["1", "2"];
    readonly b: "3";
    readonly c: "4";
 }
*/


// 3.按key=value模式切割返回属性的索引类型，否则就是任意索引类型的Record<string, any>
type ParseParam<Param extends string> =
    Param extends `${infer Key}=${infer Value}` ? {
        [K in Key]: Value
    } : Record<string, any>;

/**
 测试ParseParam
 type ParseParamResult = ParseParam<'a=1'>

 type ParseParamResult = {
    a: "1";
 }
*/


/**
 * 4.然后对多个索引类型的合并，就是通过映射类型的语法构造一个新的索引类型
 *  OneParam、OtherParam 通过 Record<string, any> 来约束, 通过映射类型的语法构造一个新的索引类型返回, 
 *  key 继承于OneParam和OtherParam，加上 readonly 防止修改，
 *  value 值要判断下，如果是两者都有的值，那就做合并，否则分别取对应的值
 */
type MergeParams<
    OneParam extends Record<string, any>,
    OtherParam extends Record<string, any>
> = {
    readonly [Key in keyof OneParam | keyof OtherParam]: 
    Key extends keyof OneParam
        ? Key extends keyof OtherParam
            ? MergeValues<OneParam[Key], OtherParam[Key]>
            : OneParam[Key]
        : Key extends keyof OtherParam
            ? OtherParam[Key]
            : never
}

/**
  测试MergeParams
  type MergeParamsResult = MergeParams<{a: 1}, {a: 2, b: 3}>

  type MergeParamsResult = {
    readonly a: [1, 2];
    readonly b: 3;
  }
*/

/**
 * 5.类型参数 One、Other 为待合并的两个值的类型
 * 如果两个一样就返回其中一个，
 * 否则如果 Other 是一个数组就将 One 合并到数组，也就是 [One, ...Other]，
 * 否则把两个值合并成数组 [One, Other]
 */
type MergeValues<One, Other> = 
    One extends Other 
        ? One
        : Other extends unknown[]
            ? [One, ...Other]
            : [One, Other];

/**
  测试MergeValues
  type MergeValuesResult = MergeValues<'1', '1'>            => type MergeValuesResult = "1"
  type MergeValuesResult = MergeValues<'1', '2'>            => type MergeValuesResult = ["1", "2"]
  type MergeValuesResult = MergeValues<'1', ['2', '3']>     => type MergeValuesResult = ["1", "2", "3"]
 */


/**
 * 6.解决 queryObj 报错：
 * 元素隐式具有 "any" 类型，因为类型为 "string" 的表达式不能用于索引类型 "{}"。在类型 "{}" 上找不到具有类型为 "string" 的参数的索引签名。ts(7053)
 * 意思是 key 是 string 类型，但 queryObj={} 上找不到 string 类型的 key
 * 解决方案：
 * 1. tsconfig.json 添加 "suppressImplicitAnyIndexErrors": true,
 * 2. 定义一个新类型，并给 queryObj 指定该类型
        type QueryObject = {
            [key: string]: any
        }

        const queryObj: QueryObject = {};

    3. 定义一个isValidKey来限定
 */

// function isValidKey(key: string|number|symbol, object: object): key is keyof typeof object {
//     return key in object
// }

// const object = { a: 1, b: 2 };
// type AllTypeOfObj = typeof object;  // 获取到object的所有属性的索引类型
// type KeyOfAllTypeOfObj = keyof typeof object;   // 获取到所有属性组成的联合类型
// const isValidResult = isValidKey('a', object);


type QueryObject = {
    [key: string]: any
}

function parseQueryString<QueryString extends string>(queryStr: QueryString): ParseQueryString<QueryString> {
    if (!queryStr || !queryStr.length) {
        return {} as any;
    }

    const queryObj: QueryObject = {};
    const items = queryStr.split('&');
    items.forEach(item => {
        const [key, value] = item.split('=');
        if (queryObj[key]) {
            if(Array.isArray(queryObj[key])) {
                queryObj[key].push(value);
            } else {
                queryObj[key] = [queryObj[key], value]
            }
        } else {
            queryObj[key] = value;
        }
    });
    return queryObj as any;
}

const result = parseQueryString('a=1&a=2&b=2&c=3');
console.log(result)